package com.kool.kauth.service;

import com.kool.kauth.client.RedisClient;
import com.kool.kauth.common.constant.ErrorCode;
import com.kool.kauth.common.exception.AppException;
import com.kool.kauth.common.util.SecurityUtil;
import com.kool.kauth.common.util.StringUtil;
import com.kool.kauth.contant.ActionEnum;
import com.kool.kauth.contant.AuthFailEnum;
import com.kool.kauth.contant.UserStateEnum;
import com.kool.kauth.request.*;
import com.kool.kauth.vo.MqttUserVo;
import com.kool.kauth.vo.TopicAuthResp;
import com.kool.kauth.vo.TopicPermitVo;
import com.kool.kauth.vo.UserAuthResp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author : luyu
 * @date :2021/3/25 17:11
 */
@Service
@Slf4j
public class AuthService {
    @Resource
    private RedisClient redisClient;

    /**
     * 用户
     * 参数：用户
     * hash结构
     * hkey:pwd
     * hvalue:密码
     * hkey:state
     * hvalue:状态
     */
    public static final String HASH_USER = "auth:user:%s";
    /**
     * 用户验证错误次数
     * 参数：用户
     */
    public static final String INRC_USER_AUTH_ERR_TIMES = "auth:user_auth_err_times:%s";
    /**
     * 主题权限
     * 参数1：用户
     * 参数2：主题
     */
    public static final String HASH_TOPIC_AUTH = "auth:topic_auth:%s:%s";

    public static final long DAY_MILLIS = 86400000L;

    /**
     * 新增或更新用户
     *
     * @param request
     */
    public void setUser(SetUserReq request) {
        String userName = request.getUserName();
        String pwd = request.getPwd();
        Integer state = request.getState();

        //入redis
        redisClient.hput(String.format(HASH_USER, userName), "pwd", pwd);
        redisClient.hput(String.format(HASH_USER, userName), "state", state.toString());
    }

    /**
     * 删除用户，同时删除用户的主题权限
     *
     * @param request
     */
    public void deleteUser(DeleteUserReq request) {
        String userName = request.getUserName();
        //删除用户
        redisClient.delete(String.format(HASH_USER, userName));

        //删除用户的主题权限
        Set<String> keys = redisClient.scan(String.format(HASH_TOPIC_AUTH, userName, "*"));
        if (keys != null) {
            for (String key : keys) {
                redisClient.delete(key);
            }
        }
    }

    /**
     * 用户验证
     *
     * @param request
     * @return
     */
    public UserAuthResp userAuth(UserAuthReq request) {
        String userName = request.getUserName();
        String pwd = request.getPwd();
        //sha256加密
        pwd = SecurityUtil.sha(pwd);
        //根据用户名到redis查密码
        String pwdC = redisClient.hget(String.format(HASH_USER, userName), "pwd");
        String state = redisClient.hget(String.format(HASH_USER, userName), "state");
        if (StringUtil.isEmpty(pwdC) || StringUtil.isEmpty(state)) {
            return failUserAuth(userName, AuthFailEnum.USER_IS_ABSENT);
        }
        if (state.equals(Integer.toString(UserStateEnum.CLOSED.getCode()))) {
            return failUserAuth(userName, AuthFailEnum.USER_STATE_CLOSE);
        }
        //比较密码
        if (!pwd.equals(pwdC)) {
            return failUserAuth(userName, AuthFailEnum.PWD_ERROR);
        }
        return successUserAuth(userName);
    }

    /**
     * 验证通过
     *
     * @param userName
     * @return
     */
    private UserAuthResp successUserAuth(String userName) {
        UserAuthResp userAuthResp = new UserAuthResp();
        userAuthResp.setIsSuccess(1);
        //重置错误次数
        redisClient.delete(String.format(INRC_USER_AUTH_ERR_TIMES, userName));
        return userAuthResp;
    }

    /**
     * 用户验证不通过
     *
     * @param userName
     * @param userIsAbsent
     * @return
     */
    private UserAuthResp failUserAuth(String userName, AuthFailEnum userIsAbsent) {
        UserAuthResp userAuthResp = new UserAuthResp();
        userAuthResp.setIsSuccess(0);
        userAuthResp.setErrorCode(userIsAbsent.getCode());
        userAuthResp.setErrorMsg(userIsAbsent.getDesc());
        int times = (int) redisClient.inrc(String.format(INRC_USER_AUTH_ERR_TIMES, userName), DAY_MILLIS);
        userAuthResp.setErrorTimes(times);
        return userAuthResp;
    }

    /**
     * 授权主题
     *
     * @param request
     */
    public void topicPermit(TopicPermitReq request) {
        String topicName = request.getTopicName();
        String userName = request.getUserName();
        Integer canSubscribe = request.getCanSubscribe();
        Integer canPublish = request.getCanPublish();

        //保存缓存
        if (canSubscribe == 1) {
            redisClient.hput(String.format(HASH_TOPIC_AUTH, userName, topicName), ActionEnum.SUBSCRIBE.getAction(), "1");
        }
        if (canPublish == 1) {
            redisClient.hput(String.format(HASH_TOPIC_AUTH, userName, topicName), ActionEnum.PUBLISH.getAction(), "1");
        }
    }

    /**
     * 主题鉴权
     *
     * @param request
     * @return
     */
    public TopicAuthResp topicAuth(TopicAuthReq request) {
        String userName = request.getUserName();
        String action = request.getAction();
        //转权限位图下标位
        Integer bitIndex = ActionEnum.getBitIndex(action);
        if (bitIndex == null) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, String.format("无法对未知操作【%s】进行鉴权", action));
        }
        String topicName = request.getTopicName();

        //查缓存中有没有用户的主题权限
        String flag = redisClient.hget(String.format(HASH_TOPIC_AUTH, userName, topicName), action);
        if (StringUtil.isEmpty(flag)) {
            //用户没有该主题的权限
            TopicAuthResp topicAuthResp = new TopicAuthResp();
            topicAuthResp.setIsSuccess(0);
            return topicAuthResp;
        } else {
            //有权限
            TopicAuthResp topicAuthResp = new TopicAuthResp();
            topicAuthResp.setIsSuccess(1);
            return topicAuthResp;

        }
    }

    /**
     * 同步用户
     *
     * @param request
     */
    public void syncMqttUsers(SyncMqttUsersReq request) {
        log.info("开始同步用户");
        List<MqttUserVo> mqttUsers = request.getMqttUsers();
        Set<String> userKeys = new HashSet<>();

        //遍历数据库用户，新增或更新
        if (mqttUsers != null) {
            for (MqttUserVo mqttUser : mqttUsers) {
                String userName = mqttUser.getUserName();
                String pwd = mqttUser.getPwd();
                Integer state = mqttUser.getState();

                userKeys.add(String.format(HASH_USER, userName));
                redisClient.hput(String.format(HASH_USER, userName), "pwd", pwd);
                redisClient.hput(String.format(HASH_USER, userName), "state", state.toString());
            }
        }
        //扫描缓存用户
        Set<String> keys = redisClient.scan(String.format(HASH_USER, "*"));

        if (keys != null) {
            for (String key : keys) {
                //如果数据库没有，缓存有，则删除缓存
                if (!userKeys.contains(key)) {
                    redisClient.delete(key);
                }
            }
        }
    }

    /**
     * 同步主题权限
     *
     * @param request
     */
    public void syncTopicPermits(SyncTopicPermitsReq request) {
        log.info("开始同步主题权限");
        List<TopicPermitVo> topicPermits = request.getTopicPermits();
        Set<String> topicPermitKeys = new HashSet<>();

        //遍历数据库主题权限,新增或更新
        if (topicPermits != null) {
            for (TopicPermitVo topicPermit : topicPermits) {
                String topicName = topicPermit.getTopicName();
                String userName = topicPermit.getUserName();
                Integer canSubscribe = topicPermit.getCanSubscribe();
                Integer canPublish = topicPermit.getCanPublish();

                topicPermitKeys.add(String.format(HASH_TOPIC_AUTH, userName, topicName));
                //保存缓存
                if (canSubscribe == 1) {
                    redisClient.hput(String.format(HASH_TOPIC_AUTH, userName, topicName), ActionEnum.SUBSCRIBE.getAction(), "1");
                }
                if (canPublish == 1) {
                    redisClient.hput(String.format(HASH_TOPIC_AUTH, userName, topicName), ActionEnum.PUBLISH.getAction(), "1");
                }
            }
        }
        //扫描用户主题
        Set<String> keys = redisClient.scan(String.format(HASH_TOPIC_AUTH, "*", "*"));
        if (keys != null) {
            for (String key : keys) {
                //如果数据库没有，缓存有，则删除缓存
                if (!topicPermitKeys.contains(key)) {
                    redisClient.delete(key);
                }
            }
        }
    }
}
